﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Data.Linq.Mapping;

namespace Hxsoft.AppV1
{
	/// <summary>
	/// 实体列的设置类型。
	/// </summary>
	public enum EntityColumnsSetType
	{
		/// <summary>
		/// 排除指定列的设置。
		/// </summary>
		Exclude,
		/// <summary>
		/// 仅限于指定列的设置。
		/// </summary>
		Limit
	}

	public static class EntityExtensions
	{
		public static Dictionary<Type, Dictionary<string, PropertyInfo>> typeColumns = new Dictionary<Type, Dictionary<string, PropertyInfo>>();

		#region EntityType ToNewEntity<EntityType>
		public static EntityType ToNewEntity<EntityType>(this object entity) where EntityType : new()
		{
			Type sourceType = entity.GetType();
			Type targetType = typeof(EntityType);
			Dictionary<string, PropertyInfo> sourceColumns = GetEntityColumns(sourceType);
			Dictionary<string, PropertyInfo> targetColumns = GetEntityColumns(targetType);

			EntityType e = new EntityType();
			foreach (PropertyInfo targetPropertyInfo in targetColumns.Values)
			{
				if (!targetPropertyInfo.CanWrite) continue;

				PropertyInfo sourcePropertyInfo;
				if (sourceColumns.TryGetValue(targetPropertyInfo.Name, out sourcePropertyInfo))
				{
					object value = sourcePropertyInfo.GetValue(entity, null);
					targetPropertyInfo.SetValue(e, value, null);
				}
			}
			
			return e;
		}

		public static EntityType ToNewEntity<EntityType>(this object entity, HashSet<string> setPropertyNames) where EntityType : new()
		{
			Type sourceType = entity.GetType();
			Type targetType = typeof(EntityType);
			Dictionary<string, PropertyInfo> sourceColumns = GetEntityColumns(sourceType);
			Dictionary<string, PropertyInfo> targetColumns = GetEntityColumns(targetType);

			EntityType e = new EntityType();
			foreach (string colName in setPropertyNames)
			{
				PropertyInfo targetPropertyInfo;
				PropertyInfo sourcePropertyInfo;

				if (targetColumns.TryGetValue(colName, out targetPropertyInfo))
				{
					if (!targetPropertyInfo.CanWrite) continue;

					if (sourceColumns.TryGetValue(colName, out sourcePropertyInfo))
					{
						object value = sourcePropertyInfo.GetValue(entity, null);
						targetPropertyInfo.SetValue(e, value, null);
					}
				}
			}

			return e;
		}

		public static EntityType ToNewEntity<EntityType>(this object entity, EntityColumnsSetType setType, HashSet<string> propertyNames) where EntityType : new()
		{
			if (setType == EntityColumnsSetType.Limit) return ToNewEntity<EntityType>(entity, propertyNames);
			if (propertyNames == null || propertyNames.Count == 0) return ToNewEntity<EntityType>(entity);

			Type sourceType = entity.GetType();
			Type targetType = typeof(EntityType);
			Dictionary<string, PropertyInfo> sourceColumns = GetEntityColumns(sourceType);
			Dictionary<string, PropertyInfo> targetColumns = GetEntityColumns(targetType);

			EntityType e = new EntityType();
			foreach (PropertyInfo targetPropertyInfo in targetColumns.Values)
			{
				if (!targetPropertyInfo.CanWrite || propertyNames.Contains(targetPropertyInfo.Name)) continue;

				PropertyInfo sourcePropertyInfo;
				if (sourceColumns.TryGetValue(targetPropertyInfo.Name, out sourcePropertyInfo))
				{
					object value = sourcePropertyInfo.GetValue(entity, null);
					targetPropertyInfo.SetValue(e, value, null);
				}
			}

			return e;
		}
		#endregion

		#region FillByEntity<EntityType>
		public static EntityType FillByEntity<EntityType>(this EntityType entity, object filler) where EntityType : new()
		{
			Type sourceType = entity.GetType();
			Type fillerType = filler.GetType();
			Dictionary<string, PropertyInfo> sourceColumns = GetEntityColumns(sourceType);
			Dictionary<string, PropertyInfo> fillerColumns = GetEntityColumns(fillerType);

			foreach (PropertyInfo sourcePropertyInfo in sourceColumns.Values)
			{
				if (!sourcePropertyInfo.CanWrite) continue;

				PropertyInfo fillerPropertyInfo;
				if (fillerColumns.TryGetValue(sourcePropertyInfo.Name, out fillerPropertyInfo))
				{
					object value = fillerPropertyInfo.GetValue(filler, null);
					sourcePropertyInfo.SetValue(entity, value, null);
				}
			}

			return entity;
		}

		public static EntityType FillByEntity<EntityType>(this EntityType entity, object filler, HashSet<string> fillPropertyNames) where EntityType : new()
		{
			Type sourceType = entity.GetType();
			Type fillerType = filler.GetType();
			Dictionary<string, PropertyInfo> sourceColumns = GetEntityColumns(sourceType);
			Dictionary<string, PropertyInfo> fillerColumns = GetEntityColumns(fillerType);

			foreach (string colName in fillPropertyNames)
			{
				PropertyInfo sourcePropertyInfo;
				PropertyInfo fillerPropertyInfo;

				if (sourceColumns.TryGetValue(colName, out sourcePropertyInfo))
				{
					if (!sourcePropertyInfo.CanWrite) continue;

					if (fillerColumns.TryGetValue(colName, out fillerPropertyInfo))
					{
						object value = fillerPropertyInfo.GetValue(filler, null);
						sourcePropertyInfo.SetValue(entity, value, null);
					}
				}
			}

			return entity;
		}

		public static EntityType FillByEntity<EntityType>(this EntityType entity, object filler, EntityColumnsSetType setType, HashSet<string> propertyNames) where EntityType : new()
		{
			if (setType == EntityColumnsSetType.Limit) return FillByEntity<EntityType>(entity, filler, propertyNames);
			if (propertyNames == null || propertyNames.Count == 0) return FillByEntity<EntityType>(entity, filler);

			Type sourceType = entity.GetType();
			Type fillerType = filler.GetType();
			Dictionary<string, PropertyInfo> sourceColumns = GetEntityColumns(sourceType);
			Dictionary<string, PropertyInfo> fillerColumns = GetEntityColumns(fillerType);

			foreach (PropertyInfo sourcePropertyInfo in sourceColumns.Values)
			{
				if (!sourcePropertyInfo.CanWrite || propertyNames.Contains(sourcePropertyInfo.Name)) continue;

				PropertyInfo fillerPropertyInfo;
				if (fillerColumns.TryGetValue(sourcePropertyInfo.Name, out fillerPropertyInfo))
				{
					object value = fillerPropertyInfo.GetValue(filler, null);
					sourcePropertyInfo.SetValue(entity, value, null);
				}
			}

			return entity;
		}
		#endregion



		public static HashSet<string> GetEntityColumnNames(this Type type, IEnumerable<string> columnNames, bool triggerException)
		{
			Dictionary<string, PropertyInfo> columns = GetEntityColumns(type);

			HashSet<string> hsColumnNames = new HashSet<string>();
			foreach (string colName in columnNames)
			{
				if (columns.ContainsKey(colName))
					hsColumnNames.Add(colName);
				else if (triggerException)
					throw new ObjectNotExistsException(Res.Entity_NotExistsColumnName + colName);
			}

			return hsColumnNames;
		}



		public static Dictionary<string, PropertyInfo> GetEntityColumns(Type entityType)
		{
			if (typeColumns.ContainsKey(entityType))
				return typeColumns[entityType];

			Dictionary<string, PropertyInfo> properties = new Dictionary<string, PropertyInfo>();
			foreach (PropertyInfo p in entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
			{
				if (p.GetCustomAttributes(typeof(ColumnAttribute), true).Length > 0)
					properties[p.Name] = p;
			}

			typeColumns[entityType] = properties;
			return typeColumns[entityType];
		}
	}
}